import {
  BufferGeometry,
  Clock,
  Float32BufferAttribute,
  LinearFilter,
  Mesh,
  OrthographicCamera,
  RGBAFormat,
  Vector2,
  WebGLRenderTarget,
} from 'three'
import { MaskPass } from './MaskPass.js'
import { ClearMaskPass } from './MaskPass.js'
import { ShaderPass } from './ShaderPass.js'
import { CopyShader } from '../shaders/CopyShader.js'

class EffectComposer {
  constructor(renderer, renderTarget) {
    this.renderer = renderer

    if (renderTarget === undefined) {
      const parameters = {
        minFilter: LinearFilter,
        magFilter: LinearFilter,
        format: RGBAFormat,
      }

      const size = renderer.getSize(new Vector2())
      this._pixelRatio = renderer.getPixelRatio()
      this._width = size.width
      this._height = size.height

      renderTarget = new WebGLRenderTarget(
        this._width * this._pixelRatio,
        this._height * this._pixelRatio,
        parameters
      )
      renderTarget.texture.name = 'EffectComposer.rt1'
    } else {
      this._pixelRatio = 1
      this._width = renderTarget.width
      this._height = renderTarget.height
    }

    this.renderTarget1 = renderTarget
    this.renderTarget2 = renderTarget.clone()
    this.renderTarget2.texture.name = 'EffectComposer.rt2'

    this.writeBuffer = this.renderTarget1
    this.readBuffer = this.renderTarget2

    this.renderToScreen = true

    this.passes = []

    // dependencies

    if (CopyShader === undefined) {
      console.error('THREE.EffectComposer relies on CopyShader')
    }

    if (ShaderPass === undefined) {
      console.error('THREE.EffectComposer relies on ShaderPass')
    }

    this.copyPass = new ShaderPass(CopyShader)

    this.clock = new Clock()
  }

  swapBuffers() {
    const tmp = this.readBuffer
    this.readBuffer = this.writeBuffer
    this.writeBuffer = tmp
  }

  addPass(pass) {
    this.passes.push(pass)
    pass.setSize(
      this._width * this._pixelRatio,
      this._height * this._pixelRatio
    )
  }

  insertPass(pass, index) {
    this.passes.splice(index, 0, pass)
    pass.setSize(
      this._width * this._pixelRatio,
      this._height * this._pixelRatio
    )
  }

  removePass(pass) {
    const index = this.passes.indexOf(pass)

    if (index !== -1) {
      this.passes.splice(index, 1)
    }
  }

  isLastEnabledPass(passIndex) {
    for (let i = passIndex + 1; i < this.passes.length; i++) {
      if (this.passes[i].enabled) {
        return false
      }
    }

    return true
  }

  render(deltaTime) {
    // deltaTime value is in seconds

    if (deltaTime === undefined) {
      deltaTime = this.clock.getDelta()
    }

    const currentRenderTarget = this.renderer.getRenderTarget()

    let maskActive = false

    for (let i = 0, il = this.passes.length; i < il; i++) {
      const pass = this.passes[i]

      if (pass.enabled === false) continue

      pass.renderToScreen = this.renderToScreen && this.isLastEnabledPass(i)
      pass.render(
        this.renderer,
        this.writeBuffer,
        this.readBuffer,
        deltaTime,
        maskActive
      )

      if (pass.needsSwap) {
        if (maskActive) {
          const context = this.renderer.getContext()
          const stencil = this.renderer.state.buffers.stencil

          //context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff );
          stencil.setFunc(context.NOTEQUAL, 1, 0xffffffff)

          this.copyPass.render(
            this.renderer,
            this.writeBuffer,
            this.readBuffer,
            deltaTime
          )

          //context.stencilFunc( context.EQUAL, 1, 0xffffffff );
          stencil.setFunc(context.EQUAL, 1, 0xffffffff)
        }

        this.swapBuffers()
      }

      if (MaskPass !== undefined) {
        if (pass instanceof MaskPass) {
          maskActive = true
        } else if (pass instanceof ClearMaskPass) {
          maskActive = false
        }
      }
    }

    this.renderer.setRenderTarget(currentRenderTarget)
  }

  reset(renderTarget) {
    if (renderTarget === undefined) {
      const size = this.renderer.getSize(new Vector2())
      this._pixelRatio = this.renderer.getPixelRatio()
      this._width = size.width
      this._height = size.height

      renderTarget = this.renderTarget1.clone()
      renderTarget.setSize(
        this._width * this._pixelRatio,
        this._height * this._pixelRatio
      )
    }

    this.renderTarget1.dispose()
    this.renderTarget2.dispose()
    this.renderTarget1 = renderTarget
    this.renderTarget2 = renderTarget.clone()

    this.writeBuffer = this.renderTarget1
    this.readBuffer = this.renderTarget2
  }

  setSize(width, height) {
    this._width = width
    this._height = height

    const effectiveWidth = this._width * this._pixelRatio
    const effectiveHeight = this._height * this._pixelRatio

    this.renderTarget1.setSize(effectiveWidth, effectiveHeight)
    this.renderTarget2.setSize(effectiveWidth, effectiveHeight)

    for (let i = 0; i < this.passes.length; i++) {
      this.passes[i].setSize(effectiveWidth, effectiveHeight)
    }
  }

  setPixelRatio(pixelRatio) {
    this._pixelRatio = pixelRatio

    this.setSize(this._width, this._height)
  }
}

class Pass {
  constructor() {
    // if set to true, the pass is processed by the composer
    this.enabled = true

    // if set to true, the pass indicates to swap read and write buffer after rendering
    this.needsSwap = true

    // if set to true, the pass clears its buffer before rendering
    this.clear = false

    // if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer.
    this.renderToScreen = false
  }

  setSize(/* width, height */) {}

  render(/* renderer, writeBuffer, readBuffer, deltaTime, maskActive */) {
    console.error('THREE.Pass: .render() must be implemented in derived pass.')
  }
}

// Helper for passes that need to fill the viewport with a single quad.

const _camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1)

// https://github.com/mrdoob/three.js/pull/21358

const _geometry = new BufferGeometry()
_geometry.setAttribute(
  'position',
  new Float32BufferAttribute([-1, 3, 0, -1, -1, 0, 3, -1, 0], 3)
)
_geometry.setAttribute('uv', new Float32BufferAttribute([0, 2, 0, 0, 2, 0], 2))

class FullScreenQuad {
  constructor(material) {
    this._mesh = new Mesh(_geometry, material)
  }

  dispose() {
    this._mesh.geometry.dispose()
  }

  render(renderer) {
    renderer.render(this._mesh, _camera)
  }

  get material() {
    return this._mesh.material
  }

  set material(value) {
    this._mesh.material = value
  }
}

export { EffectComposer, Pass, FullScreenQuad }
